package aliyun

import (
	"bytes"
	"encoding/json"
	"encoding/xml"
	"fmt"
	"github.com/emersion/go-webdav"
	"github.com/gylang/aliyun/webdav/aliyun/model/req"
	"github.com/gylang/aliyun/webdav/aliyun/model/res"
	"github.com/gylang/aliyun/webdav/conf"
	"github.com/gylang/aliyun/webdav/model"
	"net/http"
	"strconv"
)

type AliYunWebdav struct {
	FileSystem webdav.FileSystem
}

func (a AliYunWebdav) Options(r *http.Request) (caps []string, allow []string, err error) {

	allow = []string{
		http.MethodOptions,
		http.MethodDelete,
		"PROPFIND",
		"COPY",
		"MOVE",
	}
	path := r.URL.Path
	if path == "/" {
		return nil, []string{
			http.MethodOptions,
			http.MethodDelete,
			"PROPFIND",
			"COPY",
			"MOVE",
		}, err
	}
	tFile := GetFileFromFileCache(path)

	if nil == tFile {
		return nil, []string{http.MethodOptions, http.MethodPut, "MKCOL"}, nil
	}

	if tFile.Type != "folder" {
		allow = append(allow, http.MethodHead, http.MethodGet, http.MethodPut)
	}

	return nil, allow, nil
}

func (a AliYunWebdav) HeadGet(w http.ResponseWriter, r *http.Request) error {

	path := r.URL.Path
	if path == "/" {
		return &model.HTTPError{Code: http.StatusMethodNotAllowed}
	}
	tFile := GetFileFromFileCache(path)
	if nil == tFile {
		return &model.HTTPError{Code: http.StatusNotFound, Err: fmt.Errorf("文件/文件夹不存在")}
	}
	if tFile.Type == "folder" {
		return &model.HTTPError{Code: http.StatusMethodNotAllowed}
	}
	bs, err := DoDownload(path, r, tFile.Size)
	if err != nil {
		return err
	}
	reader := bytes.NewReader(bs)
	w.Header().Set("Content-Length", strconv.FormatInt(tFile.Size, 10))
	//if tFile.Type != "" {
	//	w.Header().Set("Content-Type", fi.MIMEType)
	//}
	if !tFile.UpdatedAt.IsZero() {
		w.Header().Set("Last-Modified", tFile.UpdatedAt.UTC().Format(http.TimeFormat))
	}
	//if tFile.ETag != "" {
	//	w.Header().Set("ETag", model.ETag(fi.ETag).String())
	//}

	//if rs, ok := reader.(io.ReadSeeker); ok {
	// If it's an io.Seeker, use http.ServeContent which supports ranges
	http.ServeContent(w, r, path, tFile.UpdatedAt, reader)
	//} else {
	//	if r.Method != http.MethodHead {
	//		io.Copy(w, f)
	//	}
	//}
	return nil
}

func (a AliYunWebdav) Propfind(r *http.Request, propfind *model.Propfind, depth interface{}) (*interface{}, error) {
	// TODO: use partial error Response on error

	path := r.URL.Path
	if "/" == path {

		// 请求获取主文件夹
		rootReq := req.FileList{
			All:                   true,
			DriveId:               conf.AliConfSet.DriveId,
			ImageThumbnailProcess: "image/resize,w_400/format,jpeg",
			ImageUrlProcess:       "image/resize,w_1920/format,jpeg",
			Fields:                "*",
			VideoThumbnailProcess: "video/snapshot,t_0,f_jpg,ar_auto,w_300",
			ParentFileId:          "root",
			UrlExpireSec:          1600,
		}
		rootByte, err := DoPost(FileList, rootReq)
		if err != nil {
			return nil, &model.HTTPError{Code: http.StatusNotFound, Err: err}
		}
		var root []*res.TFile
		err = json.Unmarshal(rootByte, &root)
		if err != nil {
			return nil, &model.HTTPError{Code: http.StatusNotFound, Err: err}
		}
		AddAllInFileCache("/", root)
		// 返回文件夹数据
		var resps []model.Response
		resps = make([]model.Response, len(root))
		for i, child := range root {
			child.Path = path + "/" + child.Name
			resp, err := a.propfindFile(propfind, child)
			if err != nil {
				return nil, err
			}
			resps[i] = *resp
		}
	}
	fi := GetFileFromFileCache(path)
	if fi == nil {
		return nil, &model.HTTPError{Code: http.StatusNotFound, Err: fmt.Errorf("文件/文件夹不存在")}
	}

	var resps []model.Response
	if depth != model.DepthZero && fi.Type == "folder" {
		children, err := a.FileSystem.Readdir(path, depth == model.DepthInfinity)
		if err != nil {
			return nil, err
		}

		resps = make([]model.Response, len(children))
		for i, child := range children {
			resp, err := a.propfindFile(propfind, &child)
			if err != nil {
				return nil, err
			}
			resps[i] = *resp
		}
	} else {
		resp, err := a.propfindFile(propfind, fi)
		if err != nil {
			return nil, err
		}

		resps = []model.Response{*resp}
	}

	return model.NewMultistatus(resps...), nil
}

func (a AliYunWebdav) Proppatch(r *http.Request, pu *interface{}) (*interface{}, error) {
	panic("implement me")
}

func (a AliYunWebdav) Put(r *http.Request) (*interface{}, error) {
	panic("implement me")
}

func (a AliYunWebdav) Delete(r *http.Request) error {
	panic("implement me")
}

func (a AliYunWebdav) Mkcol(r *http.Request) error {
	panic("implement me")
}

func (a AliYunWebdav) Copy(r *http.Request, dest *interface{}, recursive, overwrite bool) (created bool, err error) {
	panic("implement me")
}

func (a AliYunWebdav) Move(r *http.Request, dest *interface{}, overwrite bool) (created bool, err error) {
	panic("implement me")
}

type PropfindFunc func(raw *model.RawXMLValue) (interface{}, error)

func (a *AliYunWebdav) propfindFile(propfind *model.Propfind, fi *res.TFile) (*model.Response, error) {
	props := make(map[xml.Name]PropfindFunc)

	props[model.ResourceTypeName] = func(*model.RawXMLValue) (interface{}, error) {
		var types []xml.Name
		if fi.Type == "folder" {
			types = append(types, model.CollectionName)
		}
		return model.NewResourceType(types...), nil
	}

	if fi.Type != "folder" {
		props[model.GetContentLengthName] = func(*model.RawXMLValue) (interface{}, error) {
			return &model.GetContentLength{Length: fi.Size}, nil
		}

		if !fi.UpdatedAt.IsZero() {
			props[model.GetLastModifiedName] = func(*model.RawXMLValue) (interface{}, error) {
				return &model.GetLastModified{LastModified: model.Time(fi.UpdatedAt)}, nil
			}
		}

		if fi.MineType != "" {
			props[model.GetContentTypeName] = func(*model.RawXMLValue) (interface{}, error) {
				return &model.GetContentType{Type: fi.MineType}, nil
			}
		}

		if fi.ETag != "" {
			props[model.GetETagName] = func(*model.RawXMLValue) (interface{}, error) {
				return &model.GetETag{ETag: model.ETag(fi.ETag)}, nil
			}
		}
	}

	return NewPropfindResponse(fi.Path, propfind, props)
}

func NewPropfindResponse(path string, propfind *model.Propfind, props map[xml.Name]PropfindFunc) (*model.Response, error) {
	resp := model.NewOKResponse(path)

	if _, ok := props[model.ResourceTypeName]; !ok {
		props[model.ResourceTypeName] = func(value *model.RawXMLValue) (interface{}, error) {
			return model.NewResourceType(), nil
		}
	}

	if propfind.PropName != nil {
		for xmlName, _ := range props {
			emptyVal := model.NewRawXMLElement(xmlName, nil, nil)
			if err := resp.EncodeProp(http.StatusOK, emptyVal); err != nil {
				return nil, err
			}
		}
	} else if propfind.AllProp != nil {
		// TODO: add support for propfind.Include
		for xmlName, f := range props {
			emptyVal := model.NewRawXMLElement(xmlName, nil, nil)

			val, err := f(emptyVal)

			code := http.StatusOK
			if err != nil {
				// TODO: don't throw away error message here
				code = model.HTTPErrorFromError(err).Code
				val = emptyVal
			}

			if err := resp.EncodeProp(code, val); err != nil {
				return nil, err
			}
		}
	} else if prop := propfind.Prop; prop != nil {
		for _, raw := range prop.Raw {
			xmlName, ok := raw.XMLName()
			if !ok {
				continue
			}

			emptyVal := model.NewRawXMLElement(xmlName, nil, nil)

			var code int
			var val interface{} = emptyVal
			f, ok := props[xmlName]
			if ok {
				if v, err := f(&raw); err != nil {
					// TODO: don't throw away error message here
					code = model.HTTPErrorFromError(err).Code
				} else {
					code = http.StatusOK
					val = v
				}
			} else {
				code = http.StatusNotFound
			}

			if err := resp.EncodeProp(code, val); err != nil {
				return nil, err
			}
		}
	} else {
		return nil, model.HTTPErrorf(http.StatusBadRequest, "webdav: request missing propname, allprop or prop element")
	}

	return resp, nil
}
